library(tidyverse)
library(sf)
library(mapview)

1 Intro

This Rmarkdown notebook aims to show some examples of how to solve the classroom proposed exercises in QGIS, but in R.

There are several ways to reach the same solution. Here we present only one of them.

2 Represent Transport Zones

Download and open TRIPSgeo_mun.gpkg and TRIPSgeo_freg.gpkg under MQAT/geo/ repository.

TRIPSgeo_mun = st_read("geo/TRIPSgeo_mun.gpkg", quiet = TRUE) # we add quiet = TRUE so we don't get annoying messages on the info
TRIPSgeo_freg = st_read("geo/TRIPSgeo_freg.gpkg", quiet = TRUE)

# you can also open directly from url from github. example:
# TRIPSgeo_mun = st_read("https://github.com/U-Shift/MQAT/raw/main/geo/TRIPSgeo_mun.gpkg")

Represent Transport Zones with Total, and with Car %.

# create Car_per variable
TRIPSgeo_mun = TRIPSgeo_mun |> mutate(Car_per = Car / Total * 100)
TRIPSgeo_freg = TRIPSgeo_freg |> mutate(Car_per = Car / Total * 100)

#Vizualize in map
mapview(TRIPSgeo_mun, zcol = "Car_per")
mapview(TRIPSgeo_freg, zcol = "Car_per", col.regions = rev(hcl.colors(9, "-Inferno"))) #palete inferno com 9 classes, reverse color ramp

3 Centroids

3.1 Geometric centroids

CENTROIDSgeo = st_centroid(TRIPSgeo_mun)
# mapview(CENTROIDSgeo)

3.2 Weigthed centroids

Get BGRI Data1 from INE website, at Área Metropolitana de Lisboa level: https://mapas.ine.pt/download/index2021.phtml

BGRI = st_read("original/BGRI21_LISBOA.gpkg", quiet = TRUE)

It is not that easy to estimate weighted centroids with R. See here.
We will make a bridge connection to QGIS to use its native function of mean coordinates.

library(qgisprocess)
# qgis_search_algorithms("mean") # search the exact function name
# qgis_get_argument_specs("native:meancoordinates") |> select(name, description) # see the required inputs

# with population
CENTROIDSpop = qgis_run_algorithm(algorithm = "native:meancoordinates",
                                  INPUT = BGRI,
                                  WEIGHT = "N_INDIVIDUOS",
                                  UID = "DTMN21")
CENTROIDSpop = st_as_sf(CENTROIDSpop)

# with buildings
CENTROIDSbuild = qgis_run_algorithm(algorithm = "native:meancoordinates",
                                  INPUT = BGRI,
                                  WEIGHT = "N_EDIFICIOS_CLASSICOS",
                                  UID = "DTMN21")
CENTROIDSbuild = st_as_sf(CENTROIDSbuild)

3.3 Compare in map

mapview(CENTROIDSgeo) + mapview(CENTROIDSpop, col.region = "red") + mapview(CENTROIDSbuild, col.region = "black")

See how the building, poulation and geometric centroids of Montijo are appart, from closer to Tagus, to the rural area.

4 Desire Lines

Download TRIPSdl_mun.gpkg

TRIPSdl_mun = st_read("geo/TRIPSdl_mun.gpkg", quiet = TRUE) 

Filter intrazonal trips, and trips with origin or desination in Lisbon.

TRIPSdl_mun = TRIPSdl_mun |> 
  filter(Origin_mun != Destination_mun) |> 
  filter(Total > 5000) # remove noise

mapview(TRIPSdl_mun, zcol = "Total", lwd = 5)
TRIPSdl_mun_noLX = TRIPSdl_mun |> 
  filter(Origin_mun != "Lisboa", Destination_mun != "Lisboa")

mapview(TRIPSdl_mun_noLX, zcol = "Total", lwd = 8)

You can replace the Total with other variable, such as Car, PTransit, and so on.

Note: The function od_oneway() aggregates oneway lines to produce bidirectional flows. By default, it returns the sum of each numeric column for each bidirectional origin-destination pair. This is better for viz purpouses.

5 Euclidean vs. Routing distance

5.1 Euclidean distance

5.1.1 Create new point at IST

IST = st_sfc(st_point(c(-9.1397404, 38.7370168)), crs = 4326)

5.1.2 Import survey and visualize

SURVEY = read.csv("geo/SURVEYist.txt", sep = "\t") # tab delimiter
SURVEY = st_as_sf(SURVEY, coords = c("lon", "lat"), crs = 4326) # transform as geo data

mapview(SURVEY, zcol = "MODE") + mapview(IST, col.region = "red", cex = 12)

5.1.3 Reproject layers

In R we can process distances in meters on-fly.

Buy here is the code to project layers from Geographic coordinates (WGS 84 - EPSG:4364) to Projected coordinates (Pseudo-Mercator - EPSG:3857, or Portuguese Tranversor-Mercator 06 - EPSG:3763).

ISTprojected = st_transform(IST, crs = 3857)
SURVEYprojected = st_transform(SURVEY, crs = 3857)

5.1.4 Straight lines and distance

Nearest point between the two layers. As we only have 1 point at IST layer, we will have the same number of lines as number of surveys = 200.

SURVEYeuclidean = st_nearest_points(SURVEY, IST, pairwise = TRUE) |> st_as_sf() # this creates lines

mapview(SURVEYeuclidean)
SURVEY$distance = st_length(SURVEYeuclidean) # compute distance and add directly in the first layer

summary(SURVEY$distance) # in meters
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   298.1  1105.9  2185.5  2658.5  3683.4  8600.0
# to remove the units - can be useful
SURVEY$distance = units::drop_units(SURVEY$distance)

The same function can be used to find the closest GIRA station to each survey home location. And also check where are the ones that are far away from GIRA.

GIRA = st_read("geo/GIRA2023.geojson", quiet = TRUE) # we can also read geojson with this function!

nearest = st_nearest_feature(SURVEY, GIRA) # creates an index of the closest GIRA station id

SURVEY$distanceGIRA = st_distance(SURVEY, GIRA[nearest,], by_element = TRUE)

mapview(SURVEY, zcol = "distanceGIRA") +
  mapview(GIRA, col.regions = "grey20", cex = 4, legend = FALSE)

5.2 Routing distance

We use the openrouteservice-r package. For that you need to create an account and get a Token / api key.

Note on how to store credentials in R
Using an API key from OpenRouteService, you should store it at your computer and never show it directly on code.
For that usethis::edit_r_environ() and paste your token as ORS_API_KEY="xxxxxxxxxxxxxxxxxxxxxx" (replace with your token).
Save the .Renviron file, and press Ctrl+Shift+F10 to restart R so it can take effect.

See the documentation for more details.

5.2.1 Distances 1 point to many points

Estimate the time and distance by foot-waking and driving-car, fastest mode, from survey locations (under 2 km2) to IST.

# devtools::install_github("GIScience/openrouteservice-r")
library(openrouteservice)
# ors_api_key(Sys.getenv("ORS_API_KEY")) # one time setup

# get coordinates variable
SURVEY$coordinates = st_coordinates(SURVEY)
IST$coordinates = st_coordinates(IST)

# Filter only the locations up to 2km euclidean
SURVEYsample = SURVEY |> filter(distance <= 2000)
# nrow(SURVEYsample) # 95

Although it is the same algorithm, here it works differently from QGIS.

There are many ways of doing this. If we want to know only time and distance, and not the route itself, we can use the ors_matrix(). See example here.
If we need the route, we should use the function ors_directions(). This one is not that easy to set-up because the function is prepared to retrieve only one result per request :( So we do a loop. Don’t worry, it is not that

ROUTES_foot = data.frame() # initial empty data frame

# loop - the origin (i) is the survey location, and the IST is always the same destination
for (i in 1:nrow(SURVEYsample)) {
  ROUTES1 = ors_directions(
    data.frame(
      lon = c(SURVEYsample$coordinates[i, 1], IST$coordinates[1, 1]),
      lat = c(SURVEYsample$coordinates[i, 2], IST$coordinates[1, 2])
    ),
    profile = "foot-walking", # or driving-car cycling-regular cycling-electric
    preference = "fastest", # or shortest
    output = "sf"
  )
  ROUTES1$distance = ROUTES1$summary[[1]]$distance # extract these values from summary
  ROUTES1$duration = ROUTES1$summary[[1]]$duration
  
  ROUTES_foot = rbind(ROUTES_foot, ROUTES1) # to keep adding in the same df
}

ROUTES_foot = ROUTES_foot |>
  select(distance, duration, geometry) |> # discard unnecessary variables
  mutate(ID = SURVEYsample$ID) # cbind with syrvey ID

Repeat the same for car-driving.

ROUTES_car = data.frame() # initial empty data frame

# loop - the origin (i) is the survey location, and the IST is always the same destination
for (i in 1:nrow(SURVEYsample)) {
  ROUTES1 = ors_directions(
    data.frame(
      lon = c(SURVEYsample$coordinates[i, 1], IST$coordinates[1, 1]),
      lat = c(SURVEYsample$coordinates[i, 2], IST$coordinates[1, 2])
    ),
    profile = "driving-car", # or cycling-regular cycling-electric foot-walking
    preference = "fastest", # or shortest
    output = "sf"
  )
  ROUTES1$distance = ROUTES1$summary[[1]]$distance # extract these values from summary
  ROUTES1$duration = ROUTES1$summary[[1]]$duration
  
  ROUTES_car = rbind(ROUTES_car, ROUTES1) # to keep adding in the same df
}

ROUTES_car = ROUTES_car |>
  select(distance, duration, geometry) |> # discard unnecessary variables
  mutate(ID = SURVEYsample$ID) # cbind with syrvey ID

5.3 Compare distances

We can compare the euclidean and routing distances that we estimated for the survey locations under 2 km.

summary(SURVEYsample$distance) # Euclidean
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   298.1   789.8  1046.1  1111.6  1470.4  1962.9
summary(ROUTES_foot$distance) # Walk
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   467.3  1009.0  1447.3  1471.8  1953.5  2769.4
summary(ROUTES_car$distance) # Car
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   435.4  1227.0  1686.7  1771.4  2210.3  3503.2

5.4 Vizualise routes

Visualize with transparency of 30%

mapview(ROUTES_foot, alpha = 0.3)
mapview(ROUTES_car, alpha = 0.3, color = "red")

We can also use the overline() function from stplanr package to break up the routes when they overline, and add them up.

library(stplanr)

# we create a value that we can later sum, it also could be the number of trips represented by this route. in this case is only one respondent per route
ROUTES_foot$trips = 1 

ROUTES_foot_overline = overline(
  ROUTES_foot,
  attrib = "trips",
  fun = sum
)

mapview(ROUTES_foot_overline, zcol = "trips", lwd = 3)
  • How many people are entering IST by the stairs near Bar de Civil?
  • And by the North gate?
  • And from Alameda stairs?

6 Buffers vs. Isochones and Service Areas

6.1 Buffer

Represent a buffer of 500 m and 2000 m from IST3.

# BUFFERist500 = st_buffer(IST, dist = 500) # non  projected - results may be weird
BUFFERist500 = geo_buffer(IST[1], dist = 500) # from stplnar, to make sure it is in meters.
BUFFERist2000 = geo_buffer(IST[1], dist = 2000)

mapview(BUFFERist500) + mapview(BUFFERist2000, alpha.regions = 0.5)

6.2 Isochrone

6.2.1 Isochrone from 1 point - distance

We use again the openrouteservice r package.

ISOCist = ors_isochrones(
  IST$coordinates,
  profile = "foot-walking",
  range_type = "distance", # or time
  range = c(500, 1000, 2000),
  output = "sf"
)

ISOCist = arrange(ISOCist, -value) # to make the larger polygons on top of the table so the are displayed behind.

mapview(ISOCist, zcol = "value", alpha.regions = 0.5)

As you can see, the distance buffer of 500m is larger than the isochrone of 500m. Actually we can measure their area of reach.

ISOCist$area = st_area(ISOCist)
BUFFERist500$area = st_area(BUFFERist500)
BUFFERist2000$area = st_area(BUFFERist2000)

ratio1 = BUFFERist500$area / ISOCist$area[ISOCist$value == 500] # 1.71
ratio2 = BUFFERist2000$area / ISOCist$area[ISOCist$value == 2000] # 1.22

The euclidean buffer of 500m is 1.71 times larger than its isochrone, and the buffer of 2000m is 1.23 times larger than its isochrone.

6.2.2 Isochrone from more than 1 point - time

For this purpose we will use the high schools dataset.

# import schools
SCHOOLS = st_read("geo/SCHOOLS_basicsec.gpkg", quiet = TRUE)

SCHOOLS$coordinates = st_coordinates(SCHOOLS) # create coordinate variable

SCHOOLShigh = SCHOOLS |>
  filter(Nivel == "Secundario") |> # filter the high schools
  filter(INF_NOME != "Escola Básica e Secundária Gil Vicente") # the building is too far apart from the OSM networks and routing cannot be fount for this school

# list of XY coordinates for ORS
coor = data.frame(lon = SCHOOLShigh$coordinates[, 1], lat = SCHOOLShigh$coordinates[, 2])

And proceed with the time isochrones, for a range of 20 min, with 5 min intervals.

coor_max5 = sample_n(coor, 5) # error of api, get a loop to overpass this!

ISOCist_5 = ors_isochrones(
  coor_max5,
  profile = "foot-walking",
  range_type = "time", # or distance
  range = 20*60, # 20 minutes in seconds
  interval = 5*60, # to have intervals of 5 minutes
  output = "sf"
)

Because openrouteservece only allows a max of 5 requests for isochrones at a time, we put it in a loop to run for all the 19 high schools.

ISOCist = data.frame() # to start with a skeleton of df

for (i in 1:nrow(coor)) {
  ISOCist_i =
    ors_isochrones(
      coor[i,],
      profile = "foot-walking",
      range_type = "time", # or distance
      range = 20 * 60, # 20 minutes in seconds
      interval = 5 * 60, # to have intervals of 5 minutes
      attributes = "area", #you can directly get area, population, and so on. see documentation
      output = "sf"
    )
  ISOCist = rbind(ISOCist, ISOCist_i) # bind the results into the skeleton, one by one
}

ISOCist = arrange(ISOCist, -value) # to make the larger polygons on top of the table so the are displayed behind.
mapview(ISOCist, zcol = "value", alpha.regions = 0.5)

And now merge this information with the schools’ names.

summary(ISOCist$area[ISOCist$value==1200])/1000000 # in km²
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   3.497   4.308   5.390   5.103   5.719   6.558

Work In Progress


  1. Base Geográfica de Referenciação de Informação, Censos 2021↩︎

  2. for speed-up purposes - api request limit up to 40 / minute↩︎

  3. Here I selected only the first variable because now we also have the coordinates information (unecessary for this procedure)↩︎

LS0tCnRpdGxlOiAiR0lTIGluIFIgLSBleGVyY2lzZXMiCmF1dGhvcjogIlIgRsOpbGl4IgpkYXRlOiAiTVFBVCAyMDIzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgIyBjb2RlX2ZvbGRpbmc6ICJoaWRlIgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzZikKbGlicmFyeShtYXB2aWV3KQpgYGAKCgojIEludHJvCgpUaGlzIFJtYXJrZG93biBub3RlYm9vayBhaW1zIHRvIHNob3cgc29tZSBleGFtcGxlcyBvZiBob3cgdG8gc29sdmUgdGhlIGNsYXNzcm9vbSBwcm9wb3NlZCBleGVyY2lzZXMgaW4gUUdJUywgYnV0IGluIFIuCgpUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIHJlYWNoIHRoZSBzYW1lIHNvbHV0aW9uLiBIZXJlIHdlIHByZXNlbnQgb25seSBvbmUgb2YgdGhlbS4KCiMgUmVwcmVzZW50IFRyYW5zcG9ydCBab25lcwoKRG93bmxvYWQgYW5kIG9wZW4gYFRSSVBTZ2VvX211bi5ncGtnYCBhbmQgYFRSSVBTZ2VvX2ZyZWcuZ3BrZ2AgdW5kZXIgW01RQVQvZ2VvL10oaHR0cHM6Ly9naXRodWIuY29tL1UtU2hpZnQvTVFBVC90cmVlL21haW4vZ2VvKSByZXBvc2l0b3J5LgoKYGBge3IgZ2V0ZGF0YTF9ClRSSVBTZ2VvX211biA9IHN0X3JlYWQoImdlby9UUklQU2dlb19tdW4uZ3BrZyIsIHF1aWV0ID0gVFJVRSkgIyB3ZSBhZGQgcXVpZXQgPSBUUlVFIHNvIHdlIGRvbid0IGdldCBhbm5veWluZyBtZXNzYWdlcyBvbiB0aGUgaW5mbwpUUklQU2dlb19mcmVnID0gc3RfcmVhZCgiZ2VvL1RSSVBTZ2VvX2ZyZWcuZ3BrZyIsIHF1aWV0ID0gVFJVRSkKCiMgeW91IGNhbiBhbHNvIG9wZW4gZGlyZWN0bHkgZnJvbSB1cmwgZnJvbSBnaXRodWIuIGV4YW1wbGU6CiMgVFJJUFNnZW9fbXVuID0gc3RfcmVhZCgiaHR0cHM6Ly9naXRodWIuY29tL1UtU2hpZnQvTVFBVC9yYXcvbWFpbi9nZW8vVFJJUFNnZW9fbXVuLmdwa2ciKQpgYGAKClJlcHJlc2VudCBUcmFuc3BvcnQgWm9uZXMgd2l0aCBUb3RhbCwgYW5kIHdpdGggQ2FyICUuCgpgYGB7ciBjYXJwZXJ9CiMgY3JlYXRlIENhcl9wZXIgdmFyaWFibGUKVFJJUFNnZW9fbXVuID0gVFJJUFNnZW9fbXVuIHw+IG11dGF0ZShDYXJfcGVyID0gQ2FyIC8gVG90YWwgKiAxMDApClRSSVBTZ2VvX2ZyZWcgPSBUUklQU2dlb19mcmVnIHw+IG11dGF0ZShDYXJfcGVyID0gQ2FyIC8gVG90YWwgKiAxMDApCgojVml6dWFsaXplIGluIG1hcAptYXB2aWV3KFRSSVBTZ2VvX211biwgemNvbCA9ICJDYXJfcGVyIikKbWFwdmlldyhUUklQU2dlb19mcmVnLCB6Y29sID0gIkNhcl9wZXIiLCBjb2wucmVnaW9ucyA9IHJldihoY2wuY29sb3JzKDksICItSW5mZXJubyIpKSkgI3BhbGV0ZSBpbmZlcm5vIGNvbSA5IGNsYXNzZXMsIHJldmVyc2UgY29sb3IgcmFtcApgYGAKCiMgQ2VudHJvaWRzCgojIyBHZW9tZXRyaWMgY2VudHJvaWRzCgpgYGB7ciBnZW9jZW50cm9pZH0KQ0VOVFJPSURTZ2VvID0gc3RfY2VudHJvaWQoVFJJUFNnZW9fbXVuKQojIG1hcHZpZXcoQ0VOVFJPSURTZ2VvKQpgYGAKCgojIyBXZWlndGhlZCBjZW50cm9pZHMKCkdldCBCR1JJIERhdGFeW0Jhc2UgR2VvZ3LDoWZpY2EgZGUgUmVmZXJlbmNpYcOnw6NvIGRlIEluZm9ybWHDp8OjbywgQ2Vuc29zIDIwMjFdIGZyb20gSU5FIHdlYnNpdGUsIGF0ICrDgXJlYSBNZXRyb3BvbGl0YW5hIGRlIExpc2JvYSogbGV2ZWw6IFtodHRwczovL21hcGFzLmluZS5wdC9kb3dubG9hZC9pbmRleDIwMjEucGh0bWxdKG1hcGFzLmluZS5wdC9kb3dubG9hZC9pbmRleDIwMjEucGh0bWwpCgpgYGB7ciBnZXRjZW5zdXN9CkJHUkkgPSBzdF9yZWFkKCJvcmlnaW5hbC9CR1JJMjFfTElTQk9BLmdwa2ciLCBxdWlldCA9IFRSVUUpCmBgYAoKSXQgaXMgbm90IHRoYXQgZWFzeSB0byBlc3RpbWF0ZSB3ZWlnaHRlZCBjZW50cm9pZHMgd2l0aCBSLiBTZWUgW2hlcmVdKGh0dHBzOi8vd3pic29jaWFsc2NpZW5jZWNlbnRlci5naXRodWIuaW8vc3BhdGlhbGx5X3dlaWdodGVkX2F2Zy8pLiAgCldlIHdpbGwgbWFrZSBhIGJyaWRnZSBjb25uZWN0aW9uIHRvIFFHSVMgdG8gdXNlIGl0cyBuYXRpdmUgZnVuY3Rpb24gb2YgbWVhbiBjb29yZGluYXRlcy4gIAoKCmBgYHtyIHdlaWdodGVkfQpsaWJyYXJ5KHFnaXNwcm9jZXNzKQojIHFnaXNfc2VhcmNoX2FsZ29yaXRobXMoIm1lYW4iKSAjIHNlYXJjaCB0aGUgZXhhY3QgZnVuY3Rpb24gbmFtZQojIHFnaXNfZ2V0X2FyZ3VtZW50X3NwZWNzKCJuYXRpdmU6bWVhbmNvb3JkaW5hdGVzIikgfD4gc2VsZWN0KG5hbWUsIGRlc2NyaXB0aW9uKSAjIHNlZSB0aGUgcmVxdWlyZWQgaW5wdXRzCgojIHdpdGggcG9wdWxhdGlvbgpDRU5UUk9JRFNwb3AgPSBxZ2lzX3J1bl9hbGdvcml0aG0oYWxnb3JpdGhtID0gIm5hdGl2ZTptZWFuY29vcmRpbmF0ZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSU5QVVQgPSBCR1JJLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgV0VJR0hUID0gIk5fSU5ESVZJRFVPUyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVSUQgPSAiRFRNTjIxIikKQ0VOVFJPSURTcG9wID0gc3RfYXNfc2YoQ0VOVFJPSURTcG9wKQoKIyB3aXRoIGJ1aWxkaW5ncwpDRU5UUk9JRFNidWlsZCA9IHFnaXNfcnVuX2FsZ29yaXRobShhbGdvcml0aG0gPSAibmF0aXZlOm1lYW5jb29yZGluYXRlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTlBVVCA9IEJHUkksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBXRUlHSFQgPSAiTl9FRElGSUNJT1NfQ0xBU1NJQ09TIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFVJRCA9ICJEVE1OMjEiKQpDRU5UUk9JRFNidWlsZCA9IHN0X2FzX3NmKENFTlRST0lEU2J1aWxkKQpgYGAKCgojIyBDb21wYXJlIGluIG1hcAoKYGBge3IgbWFwY2VudHJvaWR9Cm1hcHZpZXcoQ0VOVFJPSURTZ2VvKSArIG1hcHZpZXcoQ0VOVFJPSURTcG9wLCBjb2wucmVnaW9uID0gInJlZCIpICsgbWFwdmlldyhDRU5UUk9JRFNidWlsZCwgY29sLnJlZ2lvbiA9ICJibGFjayIpCmBgYAoKU2VlIGhvdyB0aGUgYnVpbGRpbmcsIHBvdWxhdGlvbiBhbmQgZ2VvbWV0cmljIGNlbnRyb2lkcyBvZiBNb250aWpvIGFyZSBhcHBhcnQsIGZyb20gY2xvc2VyIHRvIFRhZ3VzLCB0byB0aGUgcnVyYWwgYXJlYS4KCgojIERlc2lyZSBMaW5lcwoKRG93bmxvYWQgYFRSSVBTZGxfbXVuLmdwa2dgIAoKYGBge3IgZ2V0ZGx9ClRSSVBTZGxfbXVuID0gc3RfcmVhZCgiZ2VvL1RSSVBTZGxfbXVuLmdwa2ciLCBxdWlldCA9IFRSVUUpIApgYGAKCkZpbHRlciBpbnRyYXpvbmFsIHRyaXBzLCBhbmQgdHJpcHMgd2l0aCBvcmlnaW4gb3IgZGVzaW5hdGlvbiBpbiBMaXNib24uCgpgYGB7ciB3aXRobHh9ClRSSVBTZGxfbXVuID0gVFJJUFNkbF9tdW4gfD4gCiAgZmlsdGVyKE9yaWdpbl9tdW4gIT0gRGVzdGluYXRpb25fbXVuKSB8PiAKICBmaWx0ZXIoVG90YWwgPiA1MDAwKSAjIHJlbW92ZSBub2lzZQoKbWFwdmlldyhUUklQU2RsX211biwgemNvbCA9ICJUb3RhbCIsIGx3ZCA9IDUpCmBgYAoKYGBge3IgZmlsdGVybHh9ClRSSVBTZGxfbXVuX25vTFggPSBUUklQU2RsX211biB8PiAKICBmaWx0ZXIoT3JpZ2luX211biAhPSAiTGlzYm9hIiwgRGVzdGluYXRpb25fbXVuICE9ICJMaXNib2EiKQoKbWFwdmlldyhUUklQU2RsX211bl9ub0xYLCB6Y29sID0gIlRvdGFsIiwgbHdkID0gOCkKYGBgCgpZb3UgY2FuIHJlcGxhY2UgdGhlIGBUb3RhbGAgd2l0aCBvdGhlciB2YXJpYWJsZSwgc3VjaCBhcyBgQ2FyYCwgYFBUcmFuc2l0YCwgYW5kIHNvIG9uLgoKPiBOb3RlOiBUaGUgZnVuY3Rpb24gW2BvZF9vbmV3YXkoKWBdKGh0dHBzOi8vZG9jcy5yb3BlbnNjaS5vcmcvc3RwbGFuci9yZWZlcmVuY2Uvb2Rfb25ld2F5Lmh0bWwpIGFnZ3JlZ2F0ZXMgb25ld2F5IGxpbmVzIHRvIHByb2R1Y2UgYmlkaXJlY3Rpb25hbCBmbG93cy4gQnkgZGVmYXVsdCwgaXQgcmV0dXJucyB0aGUgc3VtIG9mIGVhY2ggbnVtZXJpYyBjb2x1bW4gZm9yIGVhY2ggYmlkaXJlY3Rpb25hbCBvcmlnaW4tZGVzdGluYXRpb24gcGFpci4gVGhpcyBpcyBiZXR0ZXIgZm9yIHZpeiBwdXJwb3VzZXMuCgojIEV1Y2xpZGVhbiB2cy4gUm91dGluZyBkaXN0YW5jZQoKIyMgRXVjbGlkZWFuIGRpc3RhbmNlCgojIyMgQ3JlYXRlIG5ldyBwb2ludCBhdCBJU1QKCmBgYHtyIGNyZWF0ZWlzdH0KSVNUID0gc3Rfc2ZjKHN0X3BvaW50KGMoLTkuMTM5NzQwNCwgMzguNzM3MDE2OCkpLCBjcnMgPSA0MzI2KQpgYGAKCiMjIyBJbXBvcnQgc3VydmV5IGFuZCB2aXN1YWxpemUKCmBgYHtyIHN1cnZleX0KU1VSVkVZID0gcmVhZC5jc3YoImdlby9TVVJWRVlpc3QudHh0Iiwgc2VwID0gIlx0IikgIyB0YWIgZGVsaW1pdGVyClNVUlZFWSA9IHN0X2FzX3NmKFNVUlZFWSwgY29vcmRzID0gYygibG9uIiwgImxhdCIpLCBjcnMgPSA0MzI2KSAjIHRyYW5zZm9ybSBhcyBnZW8gZGF0YQoKbWFwdmlldyhTVVJWRVksIHpjb2wgPSAiTU9ERSIpICsgbWFwdmlldyhJU1QsIGNvbC5yZWdpb24gPSAicmVkIiwgY2V4ID0gMTIpCmBgYAoKIyMjIFJlcHJvamVjdCBsYXllcnMKCkluIFIgd2UgY2FuIHByb2Nlc3MgZGlzdGFuY2VzIGluIG1ldGVycyBvbi1mbHkuCgpCdXkgaGVyZSBpcyB0aGUgY29kZSB0byBwcm9qZWN0IGxheWVycyBmcm9tIEdlb2dyYXBoaWMgY29vcmRpbmF0ZXMgKFdHUyA4NCAtIEVQU0c6WzQzNjRdKGh0dHBzOi8vZXBzZy5pby80MzI2KSkgdG8gUHJvamVjdGVkIGNvb3JkaW5hdGVzIChQc2V1ZG8tTWVyY2F0b3IgLSBFUFNHOlszODU3XShodHRwczovL2Vwc2cuaW8vMzg1NyksIG9yIFBvcnR1Z3Vlc2UgVHJhbnZlcnNvci1NZXJjYXRvciAwNiAtIEVQU0c6WzM3NjNdKGh0dHBzOi8vZXBzZy5pby8zNzYzKSkuCgpgYGB7ciBwcm9qZWN0bGF5ZXJzfQpJU1Rwcm9qZWN0ZWQgPSBzdF90cmFuc2Zvcm0oSVNULCBjcnMgPSAzODU3KQpTVVJWRVlwcm9qZWN0ZWQgPSBzdF90cmFuc2Zvcm0oU1VSVkVZLCBjcnMgPSAzODU3KQpgYGAKCiMjIyBTdHJhaWdodCBsaW5lcyBhbmQgZGlzdGFuY2UKCk5lYXJlc3QgcG9pbnQgYmV0d2VlbiB0aGUgdHdvIGxheWVycy4gQXMgd2Ugb25seSBoYXZlIDEgcG9pbnQgYXQgSVNUIGxheWVyLCB3ZSB3aWxsIGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIGxpbmVzIGFzIG51bWJlciBvZiBzdXJ2ZXlzID0gYHIgbnJvdyhTVVJWRVkpYC4KCmBgYHtyIGV1Y2Rpc3RhbmNlfQpTVVJWRVlldWNsaWRlYW4gPSBzdF9uZWFyZXN0X3BvaW50cyhTVVJWRVksIElTVCwgcGFpcndpc2UgPSBUUlVFKSB8PiBzdF9hc19zZigpICMgdGhpcyBjcmVhdGVzIGxpbmVzCgptYXB2aWV3KFNVUlZFWWV1Y2xpZGVhbikKClNVUlZFWSRkaXN0YW5jZSA9IHN0X2xlbmd0aChTVVJWRVlldWNsaWRlYW4pICMgY29tcHV0ZSBkaXN0YW5jZSBhbmQgYWRkIGRpcmVjdGx5IGluIHRoZSBmaXJzdCBsYXllcgoKc3VtbWFyeShTVVJWRVkkZGlzdGFuY2UpICMgaW4gbWV0ZXJzCgojIHRvIHJlbW92ZSB0aGUgdW5pdHMgLSBjYW4gYmUgdXNlZnVsClNVUlZFWSRkaXN0YW5jZSA9IHVuaXRzOjpkcm9wX3VuaXRzKFNVUlZFWSRkaXN0YW5jZSkKYGBgCgpUaGUgc2FtZSBmdW5jdGlvbiBjYW4gYmUgdXNlZCB0byBmaW5kIHRoZSBjbG9zZXN0IEdJUkEgc3RhdGlvbiB0byBlYWNoIHN1cnZleSBob21lIGxvY2F0aW9uLiBBbmQgYWxzbyBjaGVjayB3aGVyZSBhcmUgdGhlIG9uZXMgdGhhdCBhcmUgZmFyIGF3YXkgZnJvbSBHSVJBLgoKYGBge3IgZ2lyYWh1Yn0KR0lSQSA9IHN0X3JlYWQoImdlby9HSVJBMjAyMy5nZW9qc29uIiwgcXVpZXQgPSBUUlVFKSAjIHdlIGNhbiBhbHNvIHJlYWQgZ2VvanNvbiB3aXRoIHRoaXMgZnVuY3Rpb24hCgpuZWFyZXN0ID0gc3RfbmVhcmVzdF9mZWF0dXJlKFNVUlZFWSwgR0lSQSkgIyBjcmVhdGVzIGFuIGluZGV4IG9mIHRoZSBjbG9zZXN0IEdJUkEgc3RhdGlvbiBpZAoKU1VSVkVZJGRpc3RhbmNlR0lSQSA9IHN0X2Rpc3RhbmNlKFNVUlZFWSwgR0lSQVtuZWFyZXN0LF0sIGJ5X2VsZW1lbnQgPSBUUlVFKQoKbWFwdmlldyhTVVJWRVksIHpjb2wgPSAiZGlzdGFuY2VHSVJBIikgKwogIG1hcHZpZXcoR0lSQSwgY29sLnJlZ2lvbnMgPSAiZ3JleTIwIiwgY2V4ID0gNCwgbGVnZW5kID0gRkFMU0UpCmBgYAoKIyMgUm91dGluZyBkaXN0YW5jZQoKV2UgdXNlIHRoZSBbb3BlbnJvdXRlc2VydmljZS1yXShodHRwczovL2dpc2NpZW5jZS5naXRodWIuaW8vb3BlbnJvdXRlc2VydmljZS1yLykgcGFja2FnZS4gRm9yIHRoYXQgeW91IG5lZWQgdG8gW2NyZWF0ZSBhbiBhY2NvdW50XShodHRwczovL29wZW5yb3V0ZXNlcnZpY2Uub3JnL2Rldi8jL3NpZ251cCkgYW5kIGdldCBhIFRva2VuIC8gYXBpIGtleS4KCj4gKipOb3RlIG9uIGhvdyB0byBzdG9yZSBjcmVkZW50aWFscyBpbiBSKiogIApVc2luZyBhbiBBUEkga2V5IGZyb20gW09wZW5Sb3V0ZVNlcnZpY2VdKGh0dHBzOi8vb3BlbnJvdXRlc2VydmljZS5vcmcvZGV2LyMvc2lnbnVwKSwgeW91IHNob3VsZCBzdG9yZSBpdCBhdCB5b3VyIGNvbXB1dGVyIGFuZCAqKm5ldmVyIHNob3cgaXQgZGlyZWN0bHkgb24gY29kZSoqLiAgCkZvciB0aGF0IGB1c2V0aGlzOjplZGl0X3JfZW52aXJvbigpYCBhbmQgcGFzdGUgeW91ciB0b2tlbiBhcyBgT1JTX0FQSV9LRVk9Inh4eHh4eHh4eHh4eHh4eHh4eHh4eHgiYCAocmVwbGFjZSB3aXRoIHlvdXIgdG9rZW4pLiAgICAKU2F2ZSB0aGUgLlJlbnZpcm9uIGZpbGUsIGFuZCBwcmVzcyBgQ3RybCtTaGlmdCtGMTBgIHRvIHJlc3RhcnQgUiBzbyBpdCBjYW4gdGFrZSBlZmZlY3QuCgpTZWUgdGhlIFtkb2N1bWVudGF0aW9uXShodHRwczovL2dpc2NpZW5jZS5naXRodWIuaW8vb3BlbnJvdXRlc2VydmljZS1yL2FydGljbGVzL29wZW5yb3V0ZXNlcnZpY2UuaHRtbCkgZm9yIG1vcmUgZGV0YWlscy4KCiMjIyBEaXN0YW5jZXMgMSBwb2ludCB0byBtYW55IHBvaW50cwoKRXN0aW1hdGUgdGhlIHRpbWUgYW5kIGRpc3RhbmNlIGJ5IGBmb290LXdha2luZ2AgYW5kIGBkcml2aW5nLWNhcmAsIGBmYXN0ZXN0YCBtb2RlLCBmcm9tIHN1cnZleSBsb2NhdGlvbnMgKHVuZGVyIDIga21eW2ZvciBzcGVlZC11cCBwdXJwb3NlcyAtIGFwaSByZXF1ZXN0IGxpbWl0IHVwIHRvIDQwIC8gbWludXRlXSkgdG8gSVNULgoKYGBge3Igcm91dGluZ2RhdGFwcmVwfQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiR0lTY2llbmNlL29wZW5yb3V0ZXNlcnZpY2UtciIpCmxpYnJhcnkob3BlbnJvdXRlc2VydmljZSkKIyBvcnNfYXBpX2tleShTeXMuZ2V0ZW52KCJPUlNfQVBJX0tFWSIpKSAjIG9uZSB0aW1lIHNldHVwCgojIGdldCBjb29yZGluYXRlcyB2YXJpYWJsZQpTVVJWRVkkY29vcmRpbmF0ZXMgPSBzdF9jb29yZGluYXRlcyhTVVJWRVkpCklTVCRjb29yZGluYXRlcyA9IHN0X2Nvb3JkaW5hdGVzKElTVCkKCiMgRmlsdGVyIG9ubHkgdGhlIGxvY2F0aW9ucyB1cCB0byAya20gZXVjbGlkZWFuClNVUlZFWXNhbXBsZSA9IFNVUlZFWSB8PiBmaWx0ZXIoZGlzdGFuY2UgPD0gMjAwMCkKIyBucm93KFNVUlZFWXNhbXBsZSkgIyA5NQpgYGAKCgpBbHRob3VnaCBpdCBpcyB0aGUgc2FtZSBhbGdvcml0aG0sIGhlcmUgaXQgd29ya3MgZGlmZmVyZW50bHkgZnJvbSBRR0lTLgoKVGhlcmUgYXJlIG1hbnkgd2F5cyBvZiBkb2luZyB0aGlzLiBJZiB3ZSB3YW50IHRvIGtub3cgb25seSB0aW1lIGFuZCBkaXN0YW5jZSwgYW5kICoqbm90IHRoZSByb3V0ZSoqIGl0c2VsZiwgd2UgY2FuIHVzZSB0aGUgYG9yc19tYXRyaXgoKWAuIFNlZSBleGFtcGxlIFtoZXJlXShodHRwczovL3dlYi50ZWNuaWNvLnVsaXNib2EucHQvfnJvc2FtZmVsaXgvZ2lzL3RyaXBzL3RpbWVkaXN0YW5jZW1hdHJpeC5odG1sI0Rpc3RhbmNlX2FuZF90aW1lX21hdHJpeCkuICAKSWYgd2UgbmVlZCB0aGUgcm91dGUsIHdlIHNob3VsZCB1c2UgdGhlIGZ1bmN0aW9uIGBvcnNfZGlyZWN0aW9ucygpYC4gVGhpcyBvbmUgaXMgbm90IHRoYXQgZWFzeSB0byBzZXQtdXAgYmVjYXVzZSB0aGUgZnVuY3Rpb24gaXMgcHJlcGFyZWQgdG8gcmV0cmlldmUgb25seSBvbmUgcmVzdWx0IHBlciByZXF1ZXN0IDooIFNvIHdlIGRvIGEgbG9vcC4gRG9uJ3Qgd29ycnksIGl0IGlzIG5vdCB0aGF0CgpgYGB7ciBvcnNsb29wMSwgZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQpST1VURVNfZm9vdCA9IGRhdGEuZnJhbWUoKSAjIGluaXRpYWwgZW1wdHkgZGF0YSBmcmFtZQoKIyBsb29wIC0gdGhlIG9yaWdpbiAoaSkgaXMgdGhlIHN1cnZleSBsb2NhdGlvbiwgYW5kIHRoZSBJU1QgaXMgYWx3YXlzIHRoZSBzYW1lIGRlc3RpbmF0aW9uCmZvciAoaSBpbiAxOm5yb3coU1VSVkVZc2FtcGxlKSkgewogIFJPVVRFUzEgPSBvcnNfZGlyZWN0aW9ucygKICAgIGRhdGEuZnJhbWUoCiAgICAgIGxvbiA9IGMoU1VSVkVZc2FtcGxlJGNvb3JkaW5hdGVzW2ksIDFdLCBJU1QkY29vcmRpbmF0ZXNbMSwgMV0pLAogICAgICBsYXQgPSBjKFNVUlZFWXNhbXBsZSRjb29yZGluYXRlc1tpLCAyXSwgSVNUJGNvb3JkaW5hdGVzWzEsIDJdKQogICAgKSwKICAgIHByb2ZpbGUgPSAiZm9vdC13YWxraW5nIiwgIyBvciBkcml2aW5nLWNhciBjeWNsaW5nLXJlZ3VsYXIgY3ljbGluZy1lbGVjdHJpYwogICAgcHJlZmVyZW5jZSA9ICJmYXN0ZXN0IiwgIyBvciBzaG9ydGVzdAogICAgb3V0cHV0ID0gInNmIgogICkKICBST1VURVMxJGRpc3RhbmNlID0gUk9VVEVTMSRzdW1tYXJ5W1sxXV0kZGlzdGFuY2UgIyBleHRyYWN0IHRoZXNlIHZhbHVlcyBmcm9tIHN1bW1hcnkKICBST1VURVMxJGR1cmF0aW9uID0gUk9VVEVTMSRzdW1tYXJ5W1sxXV0kZHVyYXRpb24KICAKICBST1VURVNfZm9vdCA9IHJiaW5kKFJPVVRFU19mb290LCBST1VURVMxKSAjIHRvIGtlZXAgYWRkaW5nIGluIHRoZSBzYW1lIGRmCn0KClJPVVRFU19mb290ID0gUk9VVEVTX2Zvb3QgfD4KICBzZWxlY3QoZGlzdGFuY2UsIGR1cmF0aW9uLCBnZW9tZXRyeSkgfD4gIyBkaXNjYXJkIHVubmVjZXNzYXJ5IHZhcmlhYmxlcwogIG11dGF0ZShJRCA9IFNVUlZFWXNhbXBsZSRJRCkgIyBjYmluZCB3aXRoIHN5cnZleSBJRApgYGAKClJlcGVhdCB0aGUgc2FtZSBmb3IgYGNhci1kcml2aW5nYC4KCmBgYHtyIG9yc2xvb3AyLCBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9ClJPVVRFU19jYXIgPSBkYXRhLmZyYW1lKCkgIyBpbml0aWFsIGVtcHR5IGRhdGEgZnJhbWUKCiMgbG9vcCAtIHRoZSBvcmlnaW4gKGkpIGlzIHRoZSBzdXJ2ZXkgbG9jYXRpb24sIGFuZCB0aGUgSVNUIGlzIGFsd2F5cyB0aGUgc2FtZSBkZXN0aW5hdGlvbgpmb3IgKGkgaW4gMTpucm93KFNVUlZFWXNhbXBsZSkpIHsKICBST1VURVMxID0gb3JzX2RpcmVjdGlvbnMoCiAgICBkYXRhLmZyYW1lKAogICAgICBsb24gPSBjKFNVUlZFWXNhbXBsZSRjb29yZGluYXRlc1tpLCAxXSwgSVNUJGNvb3JkaW5hdGVzWzEsIDFdKSwKICAgICAgbGF0ID0gYyhTVVJWRVlzYW1wbGUkY29vcmRpbmF0ZXNbaSwgMl0sIElTVCRjb29yZGluYXRlc1sxLCAyXSkKICAgICksCiAgICBwcm9maWxlID0gImRyaXZpbmctY2FyIiwgIyBvciBjeWNsaW5nLXJlZ3VsYXIgY3ljbGluZy1lbGVjdHJpYyBmb290LXdhbGtpbmcKICAgIHByZWZlcmVuY2UgPSAiZmFzdGVzdCIsICMgb3Igc2hvcnRlc3QKICAgIG91dHB1dCA9ICJzZiIKICApCiAgUk9VVEVTMSRkaXN0YW5jZSA9IFJPVVRFUzEkc3VtbWFyeVtbMV1dJGRpc3RhbmNlICMgZXh0cmFjdCB0aGVzZSB2YWx1ZXMgZnJvbSBzdW1tYXJ5CiAgUk9VVEVTMSRkdXJhdGlvbiA9IFJPVVRFUzEkc3VtbWFyeVtbMV1dJGR1cmF0aW9uCiAgCiAgUk9VVEVTX2NhciA9IHJiaW5kKFJPVVRFU19jYXIsIFJPVVRFUzEpICMgdG8ga2VlcCBhZGRpbmcgaW4gdGhlIHNhbWUgZGYKfQoKUk9VVEVTX2NhciA9IFJPVVRFU19jYXIgfD4KICBzZWxlY3QoZGlzdGFuY2UsIGR1cmF0aW9uLCBnZW9tZXRyeSkgfD4gIyBkaXNjYXJkIHVubmVjZXNzYXJ5IHZhcmlhYmxlcwogIG11dGF0ZShJRCA9IFNVUlZFWXNhbXBsZSRJRCkgIyBjYmluZCB3aXRoIHN5cnZleSBJRApgYGAKCmBgYHtyIGltcG9ydGV4cG9ydDEsIGluY2x1ZGU9RkFMU0V9CiMgc3Rfd3JpdGUoUk9VVEVTX2Zvb3QsICJvcmlnaW5hbC9yb3V0ZXNfZm9vdC5nZW9qc29uIikKIyBzdF93cml0ZShST1VURVNfY2FyLCAib3JpZ2luYWwvcm91dGVzX2Nhci5nZW9qc29uIikKClJPVVRFU19mb290ID0gc3RfcmVhZCgib3JpZ2luYWwvcm91dGVzX2Zvb3QuZ2VvanNvbiIsIHF1aWV0ID0gVFJVRSkKUk9VVEVTX2NhciA9IHN0X3JlYWQoIm9yaWdpbmFsL3JvdXRlc19jYXIuZ2VvanNvbiIsIHF1aWV0ID0gVFJVRSkKYGBgCgojIyBDb21wYXJlIGRpc3RhbmNlcwoKV2UgY2FuIGNvbXBhcmUgdGhlIGV1Y2xpZGVhbiBhbmQgcm91dGluZyBkaXN0YW5jZXMgdGhhdCB3ZSBlc3RpbWF0ZWQgZm9yIHRoZSBzdXJ2ZXkgbG9jYXRpb25zIHVuZGVyIDIga20uCgpgYGB7ciBkaXN0YW5jZXNzdW1tYXJ5fQpzdW1tYXJ5KFNVUlZFWXNhbXBsZSRkaXN0YW5jZSkgIyBFdWNsaWRlYW4Kc3VtbWFyeShST1VURVNfZm9vdCRkaXN0YW5jZSkgIyBXYWxrCnN1bW1hcnkoUk9VVEVTX2NhciRkaXN0YW5jZSkgIyBDYXIKYGBgCgojIyBWaXp1YWxpc2Ugcm91dGVzCgpWaXN1YWxpemUgd2l0aCB0cmFuc3BhcmVuY3kgb2YgMzAlCgpgYGB7ciBtYXByb3V0ZXN9Cm1hcHZpZXcoUk9VVEVTX2Zvb3QsIGFscGhhID0gMC4zKQptYXB2aWV3KFJPVVRFU19jYXIsIGFscGhhID0gMC4zLCBjb2xvciA9ICJyZWQiKQpgYGAKCldlIGNhbiBhbHNvIHVzZSB0aGUgYG92ZXJsaW5lKClgIFtmdW5jdGlvbiBmcm9tIHN0cGxhbnIgcGFja2FnZV0oaHR0cHM6Ly9kb2NzLnJvcGVuc2NpLm9yZy9zdHBsYW5yL3JlZmVyZW5jZS9vdmVybGluZS5odG1sKSB0byBicmVhayB1cCB0aGUgcm91dGVzIHdoZW4gdGhleSBvdmVybGluZSwgYW5kIGFkZCB0aGVtIHVwLgoKYGBge3Igb3ZlcmxpbmUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoc3RwbGFucikKCiMgd2UgY3JlYXRlIGEgdmFsdWUgdGhhdCB3ZSBjYW4gbGF0ZXIgc3VtLCBpdCBhbHNvIGNvdWxkIGJlIHRoZSBudW1iZXIgb2YgdHJpcHMgcmVwcmVzZW50ZWQgYnkgdGhpcyByb3V0ZS4gaW4gdGhpcyBjYXNlIGlzIG9ubHkgb25lIHJlc3BvbmRlbnQgcGVyIHJvdXRlClJPVVRFU19mb290JHRyaXBzID0gMSAKClJPVVRFU19mb290X292ZXJsaW5lID0gb3ZlcmxpbmUoCiAgUk9VVEVTX2Zvb3QsCiAgYXR0cmliID0gInRyaXBzIiwKICBmdW4gPSBzdW0KKQoKbWFwdmlldyhST1VURVNfZm9vdF9vdmVybGluZSwgemNvbCA9ICJ0cmlwcyIsIGx3ZCA9IDMpCmBgYAoKKiAgIEhvdyBtYW55IHBlb3BsZSBhcmUgZW50ZXJpbmcgSVNUIGJ5IHRoZSBzdGFpcnMgbmVhciAqQmFyIGRlIENpdmlsKj8gIAoqICAgQW5kIGJ5IHRoZSBOb3J0aCBnYXRlPwoqICAgQW5kIGZyb20gQWxhbWVkYSBzdGFpcnM/CgojIEJ1ZmZlcnMgdnMuIElzb2Nob25lcyBhbmQgU2VydmljZSBBcmVhcwoKIyMgQnVmZmVyCgpSZXByZXNlbnQgYSBidWZmZXIgb2YgNTAwIG0gYW5kIDIwMDAgbSBmcm9tIElTVF5bSGVyZSBJIHNlbGVjdGVkIG9ubHkgdGhlIGZpcnN0IHZhcmlhYmxlIGJlY2F1c2Ugbm93IHdlIGFsc28gaGF2ZSB0aGUgY29vcmRpbmF0ZXMgaW5mb3JtYXRpb24gKHVuZWNlc3NhcnkgZm9yIHRoaXMgcHJvY2VkdXJlKV0uCgpgYGB7ciBidWZmZXJJU1R9CgojIEJVRkZFUmlzdDUwMCA9IHN0X2J1ZmZlcihJU1QsIGRpc3QgPSA1MDApICMgbm9uICBwcm9qZWN0ZWQgLSByZXN1bHRzIG1heSBiZSB3ZWlyZApCVUZGRVJpc3Q1MDAgPSBnZW9fYnVmZmVyKElTVFsxXSwgZGlzdCA9IDUwMCkgIyBmcm9tIHN0cGxuYXIsIHRvIG1ha2Ugc3VyZSBpdCBpcyBpbiBtZXRlcnMuCkJVRkZFUmlzdDIwMDAgPSBnZW9fYnVmZmVyKElTVFsxXSwgZGlzdCA9IDIwMDApCgptYXB2aWV3KEJVRkZFUmlzdDUwMCkgKyBtYXB2aWV3KEJVRkZFUmlzdDIwMDAsIGFscGhhLnJlZ2lvbnMgPSAwLjUpCmBgYAoKIyMgSXNvY2hyb25lCgojIyMgSXNvY2hyb25lIGZyb20gMSBwb2ludCAtIGRpc3RhbmNlCgpXZSB1c2UgYWdhaW4gdGhlIGBvcGVucm91dGVzZXJ2aWNlYCByIHBhY2thZ2UuCgpgYGB7ciBpc29jaDF9CklTT0Npc3QgPSBvcnNfaXNvY2hyb25lcygKICBJU1QkY29vcmRpbmF0ZXMsCiAgcHJvZmlsZSA9ICJmb290LXdhbGtpbmciLAogIHJhbmdlX3R5cGUgPSAiZGlzdGFuY2UiLCAjIG9yIHRpbWUKICByYW5nZSA9IGMoNTAwLCAxMDAwLCAyMDAwKSwKICBvdXRwdXQgPSAic2YiCikKCklTT0Npc3QgPSBhcnJhbmdlKElTT0Npc3QsIC12YWx1ZSkgIyB0byBtYWtlIHRoZSBsYXJnZXIgcG9seWdvbnMgb24gdG9wIG9mIHRoZSB0YWJsZSBzbyB0aGUgYXJlIGRpc3BsYXllZCBiZWhpbmQuCgptYXB2aWV3KElTT0Npc3QsIHpjb2wgPSAidmFsdWUiLCBhbHBoYS5yZWdpb25zID0gMC41KQpgYGAKCkFzIHlvdSBjYW4gc2VlLCB0aGUgZGlzdGFuY2UgYnVmZmVyIG9mIDUwMG0gaXMgbGFyZ2VyIHRoYW4gdGhlIGlzb2Nocm9uZSBvZiA1MDBtLgpBY3R1YWxseSB3ZSBjYW4gbWVhc3VyZSB0aGVpciBhcmVhIG9mIHJlYWNoLgoKYGBge3IgYXJlYXJhdGlvfQpJU09DaXN0JGFyZWEgPSBzdF9hcmVhKElTT0Npc3QpCkJVRkZFUmlzdDUwMCRhcmVhID0gc3RfYXJlYShCVUZGRVJpc3Q1MDApCkJVRkZFUmlzdDIwMDAkYXJlYSA9IHN0X2FyZWEoQlVGRkVSaXN0MjAwMCkKCnJhdGlvMSA9IEJVRkZFUmlzdDUwMCRhcmVhIC8gSVNPQ2lzdCRhcmVhW0lTT0Npc3QkdmFsdWUgPT0gNTAwXSAjIDEuNzEKcmF0aW8yID0gQlVGRkVSaXN0MjAwMCRhcmVhIC8gSVNPQ2lzdCRhcmVhW0lTT0Npc3QkdmFsdWUgPT0gMjAwMF0gIyAxLjIyCmBgYAoKVGhlIGV1Y2xpZGVhbiBidWZmZXIgb2YgNTAwbSBpcyBgciByb3VuZChyYXRpbzEsIDIpYCB0aW1lcyBsYXJnZXIgdGhhbiBpdHMgaXNvY2hyb25lLCBhbmQgdGhlIGJ1ZmZlciBvZiAyMDAwbSBpcyBgciByb3VuZChyYXRpbzIsIDIpYCB0aW1lcyBsYXJnZXIgdGhhbiBpdHMgaXNvY2hyb25lLgoKCiMjIyBJc29jaHJvbmUgZnJvbSBtb3JlIHRoYW4gMSBwb2ludCAtIHRpbWUKCkZvciB0aGlzIHB1cnBvc2Ugd2Ugd2lsbCB1c2UgdGhlIGhpZ2ggc2Nob29scyBkYXRhc2V0LgoKYGBge3IgZ2V0c2Nob29sc30KIyBpbXBvcnQgc2Nob29scwpTQ0hPT0xTID0gc3RfcmVhZCgiZ2VvL1NDSE9PTFNfYmFzaWNzZWMuZ3BrZyIsIHF1aWV0ID0gVFJVRSkKClNDSE9PTFMkY29vcmRpbmF0ZXMgPSBzdF9jb29yZGluYXRlcyhTQ0hPT0xTKSAjIGNyZWF0ZSBjb29yZGluYXRlIHZhcmlhYmxlCgpTQ0hPT0xTaGlnaCA9IFNDSE9PTFMgfD4KICBmaWx0ZXIoTml2ZWwgPT0gIlNlY3VuZGFyaW8iKSB8PiAjIGZpbHRlciB0aGUgaGlnaCBzY2hvb2xzCiAgZmlsdGVyKElORl9OT01FICE9ICJFc2NvbGEgQsOhc2ljYSBlIFNlY3VuZMOhcmlhIEdpbCBWaWNlbnRlIikgIyB0aGUgYnVpbGRpbmcgaXMgdG9vIGZhciBhcGFydCBmcm9tIHRoZSBPU00gbmV0d29ya3MgYW5kIHJvdXRpbmcgY2Fubm90IGJlIGZvdW50IGZvciB0aGlzIHNjaG9vbAoKIyBsaXN0IG9mIFhZIGNvb3JkaW5hdGVzIGZvciBPUlMKY29vciA9IGRhdGEuZnJhbWUobG9uID0gU0NIT09MU2hpZ2gkY29vcmRpbmF0ZXNbLCAxXSwgbGF0ID0gU0NIT09MU2hpZ2gkY29vcmRpbmF0ZXNbLCAyXSkKYGBgCgpBbmQgcHJvY2VlZCB3aXRoIHRoZSB0aW1lIGlzb2Nocm9uZXMsIGZvciBhIHJhbmdlIG9mIDIwIG1pbiwgd2l0aCA1IG1pbiBpbnRlcnZhbHMuCgpgYGB7ciBpc29jaDIsIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY29vcl9tYXg1ID0gc2FtcGxlX24oY29vciwgNSkgIyBlcnJvciBvZiBhcGksIGdldCBhIGxvb3AgdG8gb3ZlcnBhc3MgdGhpcyEKCklTT0Npc3RfNSA9IG9yc19pc29jaHJvbmVzKAogIGNvb3JfbWF4NSwKICBwcm9maWxlID0gImZvb3Qtd2Fsa2luZyIsCiAgcmFuZ2VfdHlwZSA9ICJ0aW1lIiwgIyBvciBkaXN0YW5jZQogIHJhbmdlID0gMjAqNjAsICMgMjAgbWludXRlcyBpbiBzZWNvbmRzCiAgaW50ZXJ2YWwgPSA1KjYwLCAjIHRvIGhhdmUgaW50ZXJ2YWxzIG9mIDUgbWludXRlcwogIG91dHB1dCA9ICJzZiIKKQpgYGAKCkJlY2F1c2UgYG9wZW5yb3V0ZXNlcnZlY2VgIG9ubHkgYWxsb3dzIGEgW21heCBvZiA1IHJlcXVlc3RzXShodHRwczovL29wZW5yb3V0ZXNlcnZpY2Uub3JnL3Jlc3RyaWN0aW9ucy8pIGZvciBpc29jaHJvbmVzIGF0IGEgdGltZSwgd2UgcHV0IGl0IGluIGEgbG9vcCB0byBydW4gZm9yIGFsbCB0aGUgMTkgaGlnaCBzY2hvb2xzLgoKYGBge3IgaXNvY2gybG9vcCwgY2FjaGU9VFJVRX0KSVNPQ2lzdCA9IGRhdGEuZnJhbWUoKSAjIHRvIHN0YXJ0IHdpdGggYSBza2VsZXRvbiBvZiBkZgoKZm9yIChpIGluIDE6bnJvdyhjb29yKSkgewogIElTT0Npc3RfaSA9CiAgICBvcnNfaXNvY2hyb25lcygKICAgICAgY29vcltpLF0sCiAgICAgIHByb2ZpbGUgPSAiZm9vdC13YWxraW5nIiwKICAgICAgcmFuZ2VfdHlwZSA9ICJ0aW1lIiwgIyBvciBkaXN0YW5jZQogICAgICByYW5nZSA9IDIwICogNjAsICMgMjAgbWludXRlcyBpbiBzZWNvbmRzCiAgICAgIGludGVydmFsID0gNSAqIDYwLCAjIHRvIGhhdmUgaW50ZXJ2YWxzIG9mIDUgbWludXRlcwogICAgICBhdHRyaWJ1dGVzID0gImFyZWEiLCAjeW91IGNhbiBkaXJlY3RseSBnZXQgYXJlYSwgcG9wdWxhdGlvbiwgYW5kIHNvIG9uLiBzZWUgZG9jdW1lbnRhdGlvbgogICAgICBvdXRwdXQgPSAic2YiCiAgICApCiAgSVNPQ2lzdCA9IHJiaW5kKElTT0Npc3QsIElTT0Npc3RfaSkgIyBiaW5kIHRoZSByZXN1bHRzIGludG8gdGhlIHNrZWxldG9uLCBvbmUgYnkgb25lCn0KCklTT0Npc3QgPSBhcnJhbmdlKElTT0Npc3QsIC12YWx1ZSkgIyB0byBtYWtlIHRoZSBsYXJnZXIgcG9seWdvbnMgb24gdG9wIG9mIHRoZSB0YWJsZSBzbyB0aGUgYXJlIGRpc3BsYXllZCBiZWhpbmQuCmBgYApgYGB7ciBpc29jaDJtYXB9Cm1hcHZpZXcoSVNPQ2lzdCwgemNvbCA9ICJ2YWx1ZSIsIGFscGhhLnJlZ2lvbnMgPSAwLjUpCmBgYAoKQW5kIG5vdyBtZXJnZSB0aGlzIGluZm9ybWF0aW9uIHdpdGggdGhlIHNjaG9vbHMnIG5hbWVzLgoKYGBge3J9CnN1bW1hcnkoSVNPQ2lzdCRhcmVhW0lTT0Npc3QkdmFsdWU9PTEyMDBdKS8xMDAwMDAwICMgaW4ga23CsgpgYGAKCgoqV29yayBJbiBQcm9ncmVzcyo=